import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.ArrayList;

import ij.IJ;
import ij.ImagePlus;
import ij.process.ImageProcessor;

/**
*
* Bachelor thesis at the Institute of Biomedical Engineering, 
* located at the UMIT/EWZ campus in Hall in Tyrol. <br><br>
*
* This class is responsible for the manual correction after the 
* Rolling Ball operation 
* 
* @author Stefan Fuchs
*
*/
public class ManualRollingBallCorrection implements Runnable, ActionListener, WindowListener{

	//int[]
	/** The old Rolling Ball label */
	private int[] oldRollingBallLabel;
	
	/** The new Rolling Ball label*/
	private int[] newRollingBallLabel;
	
	/** Label that stores the voxels to be deleted*/
	private int[] deletedVoxelsLabel = null;
	
	/** Label that stores the voxels to be added*/
	private int[] addedVoxelsLabel = null;
	//int
	/** Height of the Image */
	private int height;

	/** Width of the Image */
	private int width;

	/** Min Roi-Prameters X-Axis */
	private int minRoiX;

	/** Max Roi-Prameters X-Axis */
	private int maxRoiX;

	/** Min Roi-Prameters Y-Axis */
	private int minRoiY;

	/** Max Roi-Prameters Y-Axis */
	private int maxRoiY;

	/** Min Roi-Prameters Z-Axis */
	private int minRoiZ;

	/** Max Roi-Prameters Z-Axis */
	private int maxRoiZ;
	
	//boolean
	/** Indicator if the thread is running*/
	private boolean running = false;
	
	//String
	/** The name of the ASF image*/
	private String fullImageTitle = null;
	
	//Collections
	/** Arraylist of manually added rolling Ball Voxels */
	private ArrayList<Voxel> manuallyAddedRollingBallVoxels = null;
	
	/** Arraylist of manually deleted Rolling Ball Voxels */
	private ArrayList<Voxel> manuallyDeletedRollingBallVoxels = null;
	
	/** The List with the ventricle voxels*/
	private ArrayList<Voxel> ventriclePoints = null;
	
	//misc
	/** The statusdialog*/
	private StatusDialog sd;
	
	/** The thread*/
	private Thread thread;
	
	/** The Modell*/
	private Modell modell;
	
	
	///////////////////////////////////////////////////////////////
	/////////// Singleton part ////////////////////////////////////
	//////////////////////////////////////////////////////////////
	
	private static ManualRollingBallCorrection instance = null;
	
	public static synchronized ManualRollingBallCorrection getInstance() {
		
		if ( instance == null) {
			instance = new ManualRollingBallCorrection();
		}
		
		return instance;
		
	}
	
	private ManualRollingBallCorrection() {
		//dummy constructor
	}
	
	///////////////////////////////////////////////////////////////
	/////////// End Singleton part ////////////////////////////////
	//////////////////////////////////////////////////////////////
	
	public void prepearRollingBallCorrection() {
		this.running = true;
		thread = new Thread(ManualRollingBallCorrection.getInstance());
		thread.start();
	}
	
	@Override
	public void run() {
		
		this.modell = Modell.getInstance();
		
		getDimensionParameters();
		
		this.sd = new StatusDialog();
		this.sd.getBtnAbort().addActionListener(this);
		this.sd.getJfDialog().addWindowListener(this);
		
		prepearRollingBallcorrection();
		
		startCorrection();
		
		visualizeCorrection();
		
		this.sd.close();
		
		this.modell.setMyocardiumLabel(Util.copy1DVoxelArry(this.newRollingBallLabel));
		this.running = false;
		
		clearAll();
	}
	
	/**
	 * Visualizes the results
	 */
	private void visualizeCorrection() {
		//Close Image if its opened
		if (ij.WindowManager.getImage(this.fullImageTitle) != null ) {
			if (ij.WindowManager.getImage(this.fullImageTitle).isVisible() ) {
				ij.WindowManager.getImage(this.fullImageTitle).changes = false;
				ij.WindowManager.getImage(this.fullImageTitle).close();
			}
		}
		
		ImagePlus manualImage = new ImagePlus(this.fullImageTitle, this.modell.getImageStack());
		
		ImageProcessor manualImageProc = manualImage.getProcessor();
		
		for ( Voxel v : this.ventriclePoints) {
			manualImage.setSlice(v.getZ());
			manualImageProc.putPixel(v.getX(), v.getY(), v.getValue());
		}
		
		manualImage.setSlice(1);
		manualImage.show();
	}
	
	/**
	 * Starts the correction process
	 */
	private void startCorrection() {
		this.sd.setTitle("Manuall Correction ...");
		this.sd.setTime("");
		this.sd.show(true);
		
		int coordinate1D;
		
		if (this.manuallyAddedRollingBallVoxels.size() > 0 ) {
			for (int i = 0; i < this.addedVoxelsLabel.length; i++) {
				this.sd.setStatus("Adding Voxels: " + ((int) (100/(double)this.addedVoxelsLabel.length) * i ) );
				if ( this.addedVoxelsLabel[i] != 0 ) {
					if (this.newRollingBallLabel[i] == 0 ) {
						this.newRollingBallLabel[i] = 4;
					}
				}
			}
		}
		
		/*
		 * Delete the Voxels if they are specified
		 */
		if (this.manuallyDeletedRollingBallVoxels.size() > 0 ) {
			for ( int i = 0 ; i < this.deletedVoxelsLabel.length ; i++ ) {
				this.sd.setStatus("Removing Voxels: " + ((int) (100/(double)this.deletedVoxelsLabel.length) * i ) );
				if ( this.deletedVoxelsLabel[i] != 0 ) {
					this.newRollingBallLabel[i] = 0;
				}
			}
		}
		
		//Writing into the Array
		for(int z = this.minRoiZ; z < this.maxRoiZ; z++){
			this.sd.setStatus("Processing Image: " + (int)(100 * (z - this.minRoiZ) / (this.maxRoiZ - this.minRoiZ)) + " %");
			for (int y = this.minRoiY; y < this.maxRoiY; y++){
				for (int x = this.minRoiX; x < this.maxRoiX; x++){
					
					coordinate1D = (int)(this.width * (this.height * z + y) + x);
					
					if (this.newRollingBallLabel[coordinate1D] == 1 ) {
						this.ventriclePoints.add(new Voxel(x,y,( z + 1 ),Modell.VENTRICLE_A_POINTS_LABEL.getRGB()));
					}
					if (this.newRollingBallLabel[coordinate1D] == 2) {
						this.ventriclePoints.add(new Voxel(x,y,( z + 1 ),Modell.VENTRICLE_B_POINTS_LABEL.getRGB()));
					}
					if (this.newRollingBallLabel[coordinate1D] == 3) {
						this.ventriclePoints.add(new Voxel(x,y,( z + 1 ),Modell.OVERLAPPING_MYOCARD_LABEL.getRGB()));
					}
					if (this.newRollingBallLabel[coordinate1D] == 4 ) {
						this.ventriclePoints.add(new Voxel(x,y,( z + 1 ),Modell.MANUALLY_ADDED_VOXELS.getRGB()));
					}
				}
			}
		}
	}
	
	/**
	 * Prepears the Datastructure
	 * 
	 */
	private void prepearRollingBallcorrection() {
		int coordinate1D;
		
		this.oldRollingBallLabel = this.modell.getMyocardiumLabel();
		
		this.ventriclePoints = new ArrayList<Voxel>();
		this.newRollingBallLabel = new int[this.oldRollingBallLabel.length];
		
		this.addedVoxelsLabel = new int[this.oldRollingBallLabel.length];
		this.deletedVoxelsLabel = new int[this.oldRollingBallLabel.length];
		
		for (int i = 0; i < this.newRollingBallLabel.length; i++) {
			
			this.addedVoxelsLabel[i] = 0;
			this.deletedVoxelsLabel[i] = 0;
			this.newRollingBallLabel[i] = this.oldRollingBallLabel[i];
			
		}
		
		for (Voxel v : this.manuallyAddedRollingBallVoxels) {
			coordinate1D =  (int)(this.width * (  this.height * v.getZ() + v.getY() ) +v.getX() );
			this.addedVoxelsLabel[coordinate1D] = 1;
		}
		
		for (Voxel v : this.manuallyDeletedRollingBallVoxels) {
			coordinate1D =  (int)(this.width * (  this.height * v.getZ() + v.getY() ) +v.getX() );
			this.deletedVoxelsLabel[coordinate1D] = 1;
		}
	}
	
	private void clearAll() {
		
		this.sd = null;
		this.addedVoxelsLabel = null;
		this.deletedVoxelsLabel = null;
		this.oldRollingBallLabel = null;
		this.newRollingBallLabel = null;
		this.manuallyAddedRollingBallVoxels = null;
		this.manuallyDeletedRollingBallVoxels = null;
		
		IJ.freeMemory();
		System.gc();
	}
	
	////////////////////////
	//Setter & Getters ////
	////////////////////////
	
	/**
	 * Reads the dimension-Parameters from the model and sets them accordingly
	 */
	private void getDimensionParameters() {
		this.width = this.modell.getWidth();
		this.height = this.modell.getHeight();
		this.minRoiX = this.modell.getMinRoiX();
		this.maxRoiX = this.modell.getMaxRoiX();
		this.minRoiY = this.modell.getMinRoiY();
		this.maxRoiY = this.modell.getMaxRoiY();
		this.minRoiZ = this.modell.getMinRoiZ();
		this.maxRoiZ = this.modell.getMaxRoiZ();
	}
	
	/**
	 * check if the thread is running
	 */
	public boolean isRunning() {
		return running;
	}
	
	/**
	 * Set the image name
	 * 
	 * @param fullImageTitle
	 */
	public void setFullImageTitle(String fullImageTitle) {
		this.fullImageTitle = fullImageTitle;
	}
	
	/**
	 * Set the manually added Voxels
	 * 
	 * @param manuallyAddedRollingBallVoxels
	 */
	public void setManuallyAddedRollingBallVoxels(ArrayList<Voxel> manuallyAddedRollingBallVoxels) {
		this.manuallyAddedRollingBallVoxels = manuallyAddedRollingBallVoxels;
	}
	
	/**
	 * Set the manuall deleted Voxels
	 * 
	 * @param manuallyDeletedRollingBallVoxels
	 */
	public void setManuallyDeletedRollingBallVoxels(ArrayList<Voxel> manuallyDeletedRollingBallVoxels) {
		this.manuallyDeletedRollingBallVoxels = manuallyDeletedRollingBallVoxels;
	}
	
	///////////////////////////////////////////////
	
	// ActionListener
	
	@Override
	public void actionPerformed(ActionEvent e) {
		// Empty Block
	}
	
	///////////////////////////////////////////////

	//Windowlistener
	
	@Override
	public void windowActivated(WindowEvent e) {
		// Empty Block		
	}

	@Override
	public void windowClosed(WindowEvent e) {
		// Empty Block		
	}

	@Override
	public void windowClosing(WindowEvent e) {
		// Empty Block		
	}

	@Override
	public void windowDeactivated(WindowEvent e) {
		// Empty Block		
	}

	@Override
	public void windowDeiconified(WindowEvent e) {
		// Empty Block		
	}

	@Override
	public void windowIconified(WindowEvent e) {
		// Empty Block		
	}

	@Override
	public void windowOpened(WindowEvent e) {
		// Empty Block		
	}
}